package com.googlecode.likesql.converter;

import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;

import com.googlecode.likesql.utils.PropertiesUtils;

/**
 * 
 * @author Johny Huang
 * @since 0.1
 */
public class ConverterTable {

	private final static String EXP_PREFIX = "Converter.";

	private final static String DEFAULT_CVT_KEY = "Converter.default";
	
	private final static String DEFAULT_PROP_NAME="likesql-cvt-def.properties";
	
	private final static String CUSTOM_PROP_NAME="likesql-cvt.properties";

	private final Map classMap;

	private final Map cvtCacheMap;

	private Hashtable customTable;

	private final static ConverterTable CTABLE = new ConverterTable(
			PropertiesUtils.load(DEFAULT_PROP_NAME), PropertiesUtils
					.load(CUSTOM_PROP_NAME));

	private ConverterTable(Hashtable defaultHt, Hashtable customHt) {
		classMap = initClassCvtMap(defaultHt, customHt);
		cvtCacheMap = initCvtCacheMap();
		customTable = new Hashtable();
	}

	private Map initClassCvtMap(Hashtable defaultHt, Hashtable customHt) {
		Map result = new HashMap();
		Object element = null;
		Object key = null;
		Enumeration en = null;

		for (en = defaultHt.keys(); en.hasMoreElements();) {
			key = en.nextElement();
			element = defaultHt.get(key);
			result.put(key, element);
		}
		for (en = customHt.keys(); en.hasMoreElements();) {
			key = en.nextElement();
			element = customHt.get(key);
			result.put(key, element);
		}
		result = Collections.unmodifiableMap(result);

		return result;
	}

	private Map initCvtCacheMap() {
		Map result = new HashMap();
		if (classMap != null && !classMap.isEmpty()) {
			Object element = null;
			String key = null;
			Iterator it = classMap.values().iterator();
			try {
				while (it.hasNext()) {
					key = (String) it.next();
					if (result.get(key) == null) {
						element = Class.forName(key).newInstance();
						result.put(key, element);
					}
				}
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}

			result = Collections.unmodifiableMap(result);
		}
		return result;
	}

	/**
	 * @return
	 */
	public static ConverterTable getInstance() {
		return CTABLE;
	}

	/**
	 * Get the registered converter by class name. <br>
	 * The search sequence:<br>
	 * 1.Configuration in likesql-cvt-def.properties<br>
	 * 2.Dynamic register table<br>
	 * 3.Use default converter
	 * 
	 * @param className
	 * @return
	 */
	public Converter getConverter(String className) {
		String clazzName = className == null ? "null" : className;
		final String name = EXP_PREFIX + clazzName;
		Converter cvt = null;
		Object value = (String) classMap.get(name);
		if (value == null) {
			value = customTable.get(name);
			if (value == null) {
				cvt = getDefaultConverter();
			} else {
				cvt = (Converter) value;
			}
		} else {
			cvt = (Converter) cvtCacheMap.get(value);
		}
		return cvt;
	}

	/**
	 * Get the default converter which has a key <i>Converter.default</i>
	 * configurated in file likesql-default.properties or likesql.properties
	 * 
	 * @return
	 */
	public Converter getDefaultConverter() {
		String value = (String) classMap.get(DEFAULT_CVT_KEY);
		return (Converter) cvtCacheMap.get(value);
	}

	/**
	 * @param clazz
	 * @param cvt
	 * @return
	 * @throws IllegalArgumentException
	 */
	public Converter register(Class clazz, Converter cvt)
			throws IllegalArgumentException {
		if (clazz == null || cvt == null) {
			throw new IllegalArgumentException("Converter can not be null!");
		}
		String key = EXP_PREFIX + clazz.getName();
		return (Converter) customTable.put(key, cvt);
	}

	/**
	 * @param clazz
	 */
	public void unregister(Class clazz) {
		if (clazz != null) {
			String key = EXP_PREFIX + clazz.getName();
			customTable.remove(key);
		}
	}
}
